﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Collections;
using System.Windows.Shapes;
using System.Windows;

namespace LaTeXGrapher
{
    public class LineData
    {
        KeyValuePair<string, string> _a;
        KeyValuePair<string, string> _b;

        public string Ax { get { return _a.Key; } }
        public string Ay { get { return _a.Value; } }
        public string Bx { get { return _b.Key; } }
        public string By { get { return _b.Value; } }

        public static LineData Create(IEnumerable<KeyValuePair<string, string>> points)
        {
            var array = points.ToArray();
            if (array.Length != 2)
                throw new Exception("Two points expected in line.");

            return new LineData(array[0], array[1]);
        }
        
        public static LineData CreateSegment(IEnumerable<KeyValuePair<string, string>> points)
        {
            var array = points.ToArray();
            if (array.Length != 2)
                throw new Exception("Two points expected in line.");

            return new LineDataSegment(array[0], array[1]);
        }

        public static LineData CreateSecant(IEnumerable<string> arguments)
        {
            var array = arguments.ToArray();
            if (array.Length != 3)
                throw new Exception("Three arguments expected for a Secant in: " + string.Join(", ", array));
            var name = array[0].Trim();
            if (!Tokenizer.IsIdentifier(name))
                throw new Exception("Expected function name for first argument to Secant in: " + string.Join(", ", array));
            var a = new KeyValuePair<string,string>(array[1], name + "(" + array[1] + ")");
            var b = new KeyValuePair<string,string>(array[2], name + "(" + array[2] + ")");
            return new LineData(a, b);
        }

        public static LineData CreateTangent(IEnumerable<string> arguments)
        {
            string delta = "0.0000001";
            var array = arguments.ToArray();
            if (array.Length != 2)
                throw new Exception("Two arguments expected for a Tangent in: " + string.Join(", ", array));
            var name = array[0].Trim();
            if (!Tokenizer.IsIdentifier(name))
                throw new Exception("Expected function name for first argument to Tangent in: " + string.Join(", ", array));
            var a = new KeyValuePair<string, string>(array[1], name + "(" + array[1] + ")");
            var b = new KeyValuePair<string, string>(array[1] + "+" + delta, name + "(" + array[1] + "+" + delta + ")");
            return new LineData(a, b);
        }

        public LineData(KeyValuePair<string, string> a, KeyValuePair<string, string> b)
        {
            _a = a;
            _b = b;
        }

        protected KeyValuePair<Point, Point> GetPoints(FunctionContext context)
        {
            Point a = new Point(context.Evaluate(Ax), context.Evaluate(Ay));
            Point b = new Point(context.Evaluate(Bx), context.Evaluate(By));
            return new KeyValuePair<Point, Point>(a, b);
        }

        virtual public KeyValuePair<Point, Point>? GetLine(FunctionContext context)
        {
            var pair = GetPoints(context);
            return FitInBox(pair.Key, pair.Value, context.Min, context.Max);
        }

        static KeyValuePair<Point, Point>? FitInBox(Point a, Point b, Point min, Point max)
        {
            Vector va = new Vector(a.X, a.Y);
            Vector v = b - a;
            Point b0 = v + new Point();
            Point min0 = min - va;
            Point max0 = max - va;
            
            if (v.Y == 0) // vertical line.
            {
                if (a.Y > min.Y && a.Y < max.Y)
                    return new KeyValuePair<Point, Point>(
                        new Point(min.X, a.Y),
                        new Point(max.X, a.Y));
                return null;
            }

            double m = v.Y / v.X;

            if (v.Y == 0) // horz line
            {
                if (a.X > min.X && a.X < max.X)
                    return new KeyValuePair<Point, Point>(
                        new Point(a.X, min.Y),
                        new Point(a.X, max.Y));
                return null;
            }

            Func<double, double> fx = x => m * x;
            Func<double, double> fy = y => y / m;

            Func<double, Point> fxP = x => new Point(x, fx(x));
            Func<double, Point> fyP = y => new Point(fy(y), y);
            
            var data = OnBoth(min0, max0, fxP(min0.X), fxP(max0.X), fyP(min0.Y), fyP(max0.Y)).ToArray();
            if (data.Length != 2)
                return null;
            return new KeyValuePair<Point, Point>(data[0] + va, data[1] + va);
        }

        static IEnumerable<Point> OnBoth(Point min, Point max, params Point[] values)
        {
            foreach (var v in values)
            {
                if ((v.X >= min.X && v.X <= max.X)
                    && (v.Y >= min.Y && v.Y <= max.Y))
                    yield return v;
            }
        }
    }

    class LineDataSegment : LineData
    {
        public LineDataSegment(KeyValuePair<string, string> a, KeyValuePair<string, string> b) 
            : base(a, b)
        {
        }

        public override KeyValuePair<Point, Point>? GetLine(FunctionContext context)
        {
            return GetPoints(context);
        }
    }
}
